import type { App } from 'vue'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError, ResponseType } from 'axios'
import axios from 'axios'
import type { AxiosCreateConfig } from './config'
import { requestConfig, requestFileConfig } from './config'
import windUi from '@/components/wind-ui'
import vuex from '@/store'
import utils from '@/js/utils'
import { routeLoginPage } from '@/js/hooks'
import { getBaseUrl } from './baseUrl'
import app from '@/main'
interface RequestParams {
	url: string
	data?: {}
	query?: {},
	successToast?: boolean
	errorToast?: boolean
	responseType?: ResponseType
	loading: Boolean
	responseComplete: Boolean
	success: Function
	logger?: {}
}

const log = utils.fxDebug('service')
const tokenErrorList: number[] = [100004]
let loginErrorFlag = false

export const fxLoading = {
	fxIsLoading: false
}
let loadingQueue = 0
const showLoading = (loadingFlag = true) => {
	loadingQueue++
	if (!loadingFlag) {
		return false
	}
	windUi.$fxLoading.show()
	fxLoading.fxIsLoading = true
}
const hideLoading = () => {
	loadingQueue--
	if (loadingQueue === 0) {
		windUi.$fxLoading.close()
		fxLoading.fxIsLoading = false
	}
}

const successToast = function (message: string) {
	return new Promise(resolve => {
		resolve(message)
		windUi.$fxMessage.success(message).then(resolve)
	})
}

const errorToast = function (error: AxiosError) {
	return new Promise(resolve => {
		windUi.$fxMessage.error(error.message || 'has not error message').then(() => {
			resolve(error)
		})
	})
}
const requestInterceptorsSuccess = (config?: AxiosRequestConfig) => {
	if (config) {
		if (/^\//.test(config.url as string)) {
			log(`接口地址有误:${config.url}, 已进行格式化`)
			config.url = config.url?.replace(/^\//, '')
		}
		config.url = `${getBaseUrl()}${config.url}`
	}
	if (config && config.headers) {
		config.headers.Authorization = vuex.getters.getToken
	}
	return config
}
const requestInterceptorsError = (error: AxiosError) => {
	return Promise.reject(error)
}
const responseInterceptorsSuccess = (response: AxiosResponse) => {
	if (response.config.responseType === 'arraybuffer') {
		if (response.headers['content-disposition']) {
			return response
		}
		const responseData = response.data
		const enc = new TextDecoder('utf-8')
		response = utils.JSONparse(enc.decode(new Uint8Array(responseData)), 'object') as AxiosResponse
		if (typeof response !== 'object') {
			response = utils.JSONparse(response, 'object') as AxiosResponse
		}
	}
	normalizationMesApiFormat(response)
	if (response && response.data && response.data.result) {
		return response.data
	} else {
		return Promise.reject(response.data)
	}
}
const responseInterceptorsError = (error: AxiosError) => {
	normalizationMesApiFormat(error.response!)
	return Promise.reject(error.response?.data)
}
const normalizationMesApiFormat = (response: AxiosResponse) => {
	if (utils.isDef(response.data.resultCode)) {
		response.data.result = response.data.resultCode
		response.data.message = response.data.msg
	}
}
const service: AxiosInstance = axios.create(requestConfig as AxiosCreateConfig)
const serviceFile: AxiosInstance = axios.create(requestFileConfig as AxiosCreateConfig)
service.defaults.headers.post['Content-Type'] = 'application/json'
const serviceList: AxiosInstance[] = [service, serviceFile]
serviceList.forEach(item => {
	item.interceptors.request.use(requestInterceptorsSuccess, requestInterceptorsError)
	item.interceptors.response.use(responseInterceptorsSuccess, responseInterceptorsError)
})

enum RequestMethod {
	get = 'get',
	post = 'post',
	put = 'put',
	delete = 'delete'
}

interface ActionInfo {
	success(): void
	end: (resultType: number) => number
	error(): void
}

const $fxRequest = function (type: RequestMethod): Function {
	return function <T extends { message: string, data: object, logger: object }> (requestParams: RequestParams): Promise<T> {
		return new Promise((resolve, reject) => {
			(requestParams.loading !== false) && showLoading()
			let actionInfo: ActionInfo | null = null
			const { logger } = requestParams
			if (logger) {
				actionInfo = app.config.globalProperties.$fxLogger.actionInfo(logger)
			}
			service<T>({
				method: type,
				url: requestParams.url,
				data: requestParams.data || null,
				params: requestParams.query || {}
			}).then(res => {
				(requestParams.loading !== false) && hideLoading()
				if (requestParams.successToast) {
					successToast(res.message)
				}
				if (actionInfo) {
					actionInfo.success()
					actionInfo = null
				}
				let _res = requestParams.responseComplete ? res : res.data
				if (requestParams.success && typeof requestParams.success === 'function') {
					_res = requestParams.success(_res)
				}
				resolve(_res as T)
				return _res
			}).catch(error => {
				(requestParams.loading !== false) && hideLoading()
				if (actionInfo) {
					actionInfo.error()
					actionInfo = null
				}
				if (tokenErrorList.includes(error.code)) {
					if (!loginErrorFlag) {
						loginErrorFlag = true
						errorToast(error).then(() => {
							routeLoginPage()
						})
					}
				} else if (requestParams.errorToast !== false) {
					errorToast(error).then(() => {
						reject(error)
					})
				} else {
					reject(error)
				}
			})
		})
	}
}

export const $fxRequestGet = $fxRequest(RequestMethod.get)
export const $fxRequestPost = $fxRequest(RequestMethod.post)
export const $fxRequestPut = $fxRequest(RequestMethod.put)
export const $fxRequestDel = $fxRequest(RequestMethod.delete)

const $fxRequestFileHandler = function (type: RequestMethod): Function {
	return function <T extends { result: unknown, message: string, data: object }> (requestParams: RequestParams): Promise<T> {
		return new Promise((resolve, reject) => {
			const params = new FormData()
			if (requestParams.data) {
				Object.keys(requestParams.data as { [key: string]: string }).forEach((item: string) => {
					params.append(item, (requestParams.data as { [key: string]: string })[item])
				})
			}
			(requestParams.loading !== false) && showLoading()
			let actionInfo: ActionInfo | null = null
			const { logger } = requestParams
			if (logger) {
				actionInfo = app.config.globalProperties.$fxLogger.actionInfo(logger)
			}
			service<T>({
				method: type,
				url: requestParams.url,
				data: params || null,
				responseType: requestParams.responseType || 'json',
				params: requestParams.query || {}
			}).then(res => {
				(requestParams.loading !== false) && hideLoading()
				if (actionInfo) {
					const resulteType: number = res.result ? 1 : 0
					actionInfo.end(resulteType)
					actionInfo = null
				}
				if (requestParams.successToast) {
					successToast(res.message)
				}
				if (requestParams.responseType === 'arraybuffer') {
					resolve(res)
				} else {
					resolve(res.data as T)
				}
			}).catch(error => {
				(requestParams.loading !== false) && hideLoading()
				if (actionInfo) {
					actionInfo.error()
					actionInfo = null
				}
				if (requestParams.errorToast) {
					errorToast(error).then(() => {
						reject(error)
					})
				} else {
					reject(error)
				}
			})
		})
	}
}

export const $fxRequestFile = $fxRequestFileHandler(RequestMethod.post)


const install = (app: App): void => {
	app.config.globalProperties.$fxRequestGet = $fxRequestGet
	app.config.globalProperties.$fxRequestPut = $fxRequestPut
	app.config.globalProperties.$fxRequestPost = $fxRequestPost
	app.config.globalProperties.$fxRequestDel = $fxRequestDel
	app.config.globalProperties.$fxRequestFile = $fxRequestFile
}

export default {
	install,
	requestPost: $fxRequestPost,
	requestGet: $fxRequestGet,
	requestPut: $fxRequestPut,
	requestDel: $fxRequestDel,
	requestFile: $fxRequestFile
}
